package no.hig.ekm.ircclient;

import java.beans.PropertyVetoException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ResourceBundle;

import javax.swing.JInternalFrame;

import jerklib.Channel;
import jerklib.ServerInformation;
import jerklib.Session;
import jerklib.events.ChannelListEvent;
import jerklib.events.ConnectionCompleteEvent;
import jerklib.events.ConnectionLostEvent;
import jerklib.events.CtcpEvent;
import jerklib.events.ErrorEvent;
import jerklib.events.ErrorEvent.ErrorType;
import jerklib.events.IRCEvent;
import jerklib.events.IRCEvent.Type;
import jerklib.events.InviteEvent;
import jerklib.events.JoinCompleteEvent;
import jerklib.events.JoinEvent;
import jerklib.events.KickEvent;
import jerklib.events.MessageEvent;
import jerklib.events.MotdEvent;
import jerklib.events.NickChangeEvent;
import jerklib.events.NickInUseEvent;
import jerklib.events.NickListEvent;
import jerklib.events.NoticeEvent;
import jerklib.events.QuitEvent;
import jerklib.events.PartEvent;
import jerklib.events.ServerInformationEvent;
import jerklib.events.ServerVersionEvent;
import jerklib.events.TopicEvent;
import jerklib.events.WhoisEvent;
import jerklib.listeners.IRCEventListener;

import jerklib.events.modes.ModeAdjustment;
import jerklib.events.modes.ModeAdjustment.Action;
import jerklib.events.modes.ModeEvent;

/**
 * Class which handels our responses for all the events a server want to send
 * to us.
 * 
 * @author eigil, kristofers, morten
 *
 */
public class ServerEventListener implements IRCEventListener{
	//Static resourceBundle used for internationalization
	private static ResourceBundle messages;
	
	//Serverinformation and restrictions
	ServerInformation si;
	private int in_use = 0;
	
	//The serverMessages window
	private ChannelWindow serverMessages;
	private StringBuilder cachedMessages;
	
	//Hashmap with all the GUI-windows for the channels.
	private HashMap<String, ChannelWindow> channels; 
	private HashMap<String, String> cache;
	
	/**
	 * Constructor, whic sets up the basic internal datastructures.
	 * 
	 * @param sm - A reference to a ChannelWindow, which is used for serverMessages.
	 */
	ServerEventListener() {
		si = new ServerInformation();
		channels = new HashMap<String, ChannelWindow>();
		cache = new HashMap<String, String>();
		cachedMessages = new StringBuilder();
	}

	/**
	 * Method to respond to a vast amount of different events. This function is called when
	 * an event enters the client.
	 * 
	 * @see jerklib.listeners.IRCEventListener#receiveEvent(jerklib.events.IRCEvent)
	 */
	@Override
	public void receiveEvent(IRCEvent e) {
		if(e.getType() == Type.DEFAULT) {
			int i = 0, type = 0;
			String[] parts = e.getRawEventData().split(" ");
			for(i = 0; i < parts.length; i++) {
				System.out.println(i + ": " + parts[i]);
				if(parts[i].equals("KICK")) {
					ChannelWindow cw = channels.get(parts[i+1]);
					cw.removeNick(parts[i+2]);
					cw.addChatLine(parts[i+2] + " (" + parts[i-1] + ") " +
							messages.getString("ircEventListenerKicked") +  " " + 
							parts[i+3].substring(1), Fonts.getFont(Fonts.CHATSTATUS));
					if(parts[i+2].equals(e.getSession().getNick()))
						Main.win.tree.removeChannel(e.getSession().getConnectedHostName(), cw);
					break;
				}
			}
			
		//If we successfully connected to a server, do nothing special (Yet :) )
		} else if (e.getType() == Type.CONNECT_COMPLETE) {
			ConnectionCompleteEvent cce = (ConnectionCompleteEvent) e;
			serverMessages = new ChannelWindow(cce.getSession(), cce.getActualHostName(), "", ChannelWindow.SERVER_MESSAGES);
			Main.win.addMDIWin(serverMessages);
			Main.win.tree.addServer(serverMessages);
			serverMessages.addChatLine(cachedMessages.toString(), Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Connected to " + cce.getActualHostName() + "!", Fonts.getFont(Fonts.SERVER));
			Main.cm.joinComplete(cce.getSession());
			if(Main.cm.isInvisible(cce.getSession()))
				cce.getSession().mode(cce.getSession().getNick(), "+i");			
			//Activate the server window
			serverMessages.toFront();
			try {
				serverMessages.setSelected(true);
			} catch (PropertyVetoException e1) {
				e1.printStackTrace();
			}
			
			
		//A notice from the server is added to the serverMessages window
		} else if(e.getType() == Type.NOTICE) {
			NoticeEvent ne = (NoticeEvent) e;
			if(serverMessages != null)
				serverMessages.addChatLine(ne.getNoticeMessage(), Fonts.getFont(Fonts.SERVER));
			else
				cachedMessages.append(ne.getNoticeMessage() + "\n"); 
			
		//A serverVersionEvent is posted into the serverMessages window
		} else if(e.getType() == Type.SERVER_VERSION_EVENT) {
			ServerVersionEvent sve = (ServerVersionEvent) e;
			serverMessages.addChatLine(sve.getHostName() + " " + 
					messages.getString("ircEventListenerIsRunning") + " " + 
					sve.getVersion(), Fonts.getFont(Fonts.SERVER));
			
		//ServerInformation is stored into the serverInformation structure
		} else if(e.getType() == Type.SERVER_INFORMATION) {
			si.parseServerInfo(e.getRawEventData());

		//CTCP-Events. Actions are displayed.
		} else if(e.getType() == Type.CTCP_EVENT) {
			CtcpEvent ce = (CtcpEvent) e;
			if(ce.getCtcpString().startsWith("ACTION")) {
				ChannelWindow cw = channels.get(ce.getChannel().getName());
				cw.addChatLine("* " + ce.getNick() + ce.getCtcpString().substring(6), Fonts.getFont(Fonts.ACTIONS));
			} else {
				System.out.println("Uncatched ctcp-event: "	+ ce.getCtcpString());
			}
			
		//The message of the day is posted to the ServerMessages window
		} else if(e.getType() == Type.MOTD) {
			MotdEvent me = (MotdEvent) e;
			serverMessages.addChatLine(me.getMotdLine(), Fonts.getFont(Fonts.SERVER));
			
		//A join-complete event spawns a new chatwindow, and stores a reference to it
		//  into the channels-hashmap. 
		} else if(e.getType() == Type.JOIN_COMPLETE) {
			JoinCompleteEvent jce = (JoinCompleteEvent) e;
			String topic = cache.get("TOPIC:" + jce.getChannel());
			if(topic == null) topic = new String("UnknownTopic");
			ChannelWindow chan = new ChannelWindow(jce.getSession(), jce.getChannel().getName() + " - " + topic, 
					jce.getChannel().getName(), ChannelWindow.CHANNEL_CHAT);
			Main.win.addMDIWin(chan);
			Main.win.tree.addChannel(jce.getSession().getConnectedHostName(), chan);
			chan.toFront();
			channels.put(jce.getChannel().getName(), chan);
			serverMessages.addChatLine(messages.getString("ircEventListenerSuccessfulJoin") + 
					" \"" + jce.getChannel().getName() + "\".", Fonts.getFont(Fonts.SERVER));
			//Activate the server window
			chan.toFront();
			try {
				chan.setSelected(true);
			} catch (PropertyVetoException e1) {
				e1.printStackTrace();
			}
			
		//If a player joins the channel.
		} else if(e.getType() == Type.JOIN) {
			JoinEvent je = (JoinEvent) e;
			ChannelWindow cw = channels.get(je.getChannel().getName());
			cw.addChatLine(je.getNick() + "(" + je.getHostName() + ") " + 
					messages.getString("ircEventListenerJoined"), Fonts.getFont(Fonts.CHATSTATUS));
			cw.addNick(je.getNick());

		//If a player wuits the channels.
		} else if(e.getType() == Type.QUIT) {
			ChannelWindow cw;
			QuitEvent qe = (QuitEvent) e;
			List<Channel> qchannels = qe.getChannelList();
			for(int i = 0; i < qchannels.size(); i++) {
				cw = channels.get(qchannels.get(i).getName());
				cw.addChatLine(qe.getNick() + " (" + qe.getUserName() + "@" + qe.getHostName() + ") " 
						+ messages.getString("ircEventListenerQuit") + " [" + qe.getQuitMessage() + "]", 
						Fonts.getFont(Fonts.CHATSTATUS));
				cw.removeNick(qe.getNick());
			}
			
		//If a player parts from a channel
		} else if(e.getType() == Type.PART) {
			ChannelWindow cw;
			PartEvent pe = (PartEvent) e;
			cw = channels.get(pe.getChannelName());
			cw.removeNick(pe.getWho());
			cw.addChatLine(pe.getWho() + "(" + pe.getUserName() + "@" + pe.getHostName() + ") " + 
					messages.getString("ircEventListenerPart") + " [" + pe.getPartMessage() + "]", 
					Fonts.getFont(Fonts.CHATSTATUS));
			if (pe.getWho().equals(cw.getSession().getNick())) {
				Main.win.tree.removeChannel(cw.getSession().getConnectedHostName(), cw);
				cw.dispose();
			}
			
		//If we recieves an answer of a whois event
		} else if(e.getType() == Type.WHOIS_EVENT) {
			WhoisEvent wie = (WhoisEvent) e;
			List<String> ch = wie.getChannelNames();
			String channels = new String("");
			for(int i = 0; i < ch.size(); i++) {
				channels = channels + " " + ch.get(i);
			}
			serverMessages.addChatLine("Whois " + wie.getUser() + " (" + wie.getHost() 
					+ "):", Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Realname: " + wie.getRealName(), 
					Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Server: " + wie.whoisServer(), 
					Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Serverinfo: " + wie.whoisServerInfo(), 
					Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Idle: " + wie.secondsIdle(), 
					Fonts.getFont(Fonts.SERVER));
			serverMessages.addChatLine("Channels: " + channels, 
					Fonts.getFont(Fonts.SERVER));
			
		//If we gets invited to a channel
		} else if(e.getType() == Type.INVITE_EVENT) {
			InviteEvent ie = (InviteEvent) e;
			ie.getSession().sayRaw("join " + ie.getChannelName());
			serverMessages.addChatLine(ie.getNick() + " invited you into " + ie.getChannelName(), 
					Fonts.getFont(Fonts.SERVER));
		
		//Store the recieved topic.
		} else if(e.getType() == Type.TOPIC) {
			TopicEvent te = (TopicEvent) e;
			ChannelWindow c = channels.get(te.getChannel().getName());
			if(c != null) {
				c.setTopic(te.getTopic());
			} else {
				cache.put("TOPIC:" + te.getChannel().getName(), te.getTopic());
			}
			
		//When a nicklist arrives, we want to give it to the correct channelWindow.
		} else if(e.getType() == Type.NICK_LIST_EVENT) {
			NickListEvent nle = (NickListEvent) e;
			List<String> nicks = nle.getNicks();
			List<String> voice = nle.getChannel().getNicksForMode(Action.PLUS, 'v');
			List<String> op = nle.getChannel().getNicksForMode(Action.PLUS, 'o');
			channels.get(nle.getChannel().getName()).setNickList(nicks, op, voice);
			//When a chanlist arrives, we want to give it to the correct channelList.
		} else if(e.getType() == Type.CHANNEL_LIST_EVENT) {
			ChannelListEvent cle = (ChannelListEvent) e;
			String chans = cle.getChannelName();
			ChannelList.addChan(chans);
									
		//An incoming chatmessage is passed to the correct channelwindow.
		} else if (e.getType() == Type.CHANNEL_MESSAGE) {
			MessageEvent me = (MessageEvent) e;
			ChannelWindow channel = channels.get(me.getChannel().getName());
			
			if(channel == null) {
				System.err.println("ERROR: Recieved message from channel I havn't joined!" + me.getChannel().getName());
			} else {
				channel.addChatLine("<" + channel.getPrefixedNick(me.getNick())
						+ "> " + me.getMessage(), Fonts.getFont(Fonts.CHANNEL));
			}
			
		//An incoming message is posted into the correct messageWindow. If this is the first time we get a message
		//  from this particular user, we spawns a new ChannelWindow.
		} else if (e.getType() == Type.PRIVATE_MESSAGE) {
			MessageEvent me = (MessageEvent) e;
			ChannelWindow channel = channels.get(me.getNick());
			
			if(channel == null) {
				channel = new ChannelWindow(me.getSession(), 
						me.getSession().getConnectedHostName() + " - " + me.getNick(), 
						me.getNick(), ChannelWindow.PRIVATE_CHAT);
				channels.put(me.getNick(), channel);
				Main.win.addMDIWin(channel);
				Main.win.tree.addChannel(me.getSession().getConnectedHostName(), channel);
			} 
			channel.addChatLine("<" + me.getNick() + "> " + me.getMessage(), Fonts.getFont(Fonts.PRIVATE));
			
		//How to handle a nick-inuse
		} else if (e.getType() == Type.NICK_IN_USE) {
			NickInUseEvent niue = (NickInUseEvent) e;
			
			if(serverMessages != null) {
				serverMessages.addChatLine(messages.getString("ircEventListenerNickOneInusePartOne") + 
							"\"" + niue.getInUseNick() + "\"" + 
							messages.getString("ircEventListenerNickOneInusePartTwo"), 
							Fonts.getFont(Fonts.SERVER));
			}
			//Disconnect if both our nicks are in use.
			if(++in_use >= 2) {
				if(serverMessages != null)
					serverMessages.addChatLine(messages.getString("ircEventListenerNickTwoInuse"), Fonts.getFont(Fonts.SERVER));
				niue.getSession().close(null);
			}
		
		//Clean up, if the connection gets lost.
		} else if (e.getType() == Type.CONNECTION_LOST) {
			if(serverMessages != null)
				serverMessages.addChatLine(messages.getString("ircEventListenerConnectionLost"), Fonts.getFont(Fonts.SERVER));
			Main.win.tree.removeChannel(e.getSession().getConnectedHostName(), serverMessages);
			if (e.getSession() != null)
				e.getSession().close(null);
			
		//Update nicklists if someone changes a nick
		} else if (e.getType() == Type.NICK_CHANGE) {
			ChannelWindow cw;
			NickChangeEvent nce = (NickChangeEvent) e;
			Object c[] = channels.values().toArray();
			
			for(int i = 0; i < c.length; i++) {
				cw = (ChannelWindow)c[i];
				cw.nickChanged(nce.getOldNick(), nce.getNewNick());
			}

		//When a mode changes
		} else if (e.getType() == Type.MODE_EVENT) {
			ModeEvent me = (ModeEvent) e;
			String channelName = "";
			//If we are connected to a channel, get its name
			if (me.getChannel() != null)
				channelName = me.getChannel().getName();
			
			System.out.println("Mode Event");
			
			if(me.getModeType() == ModeEvent.ModeType.CHANNEL) {
				ChannelWindow cw = channels.get(channelName);
				List<ModeAdjustment> modeAdjust = me.getModeAdjustments();
				String arg;
				boolean plus;
				char mode, adj;
				
				if(cw != null) {
					for(int i = 0; i < modeAdjust.size(); i++) {
						plus = modeAdjust.get(i).getAction() == ModeAdjustment.Action.PLUS;
						adj = (plus)? '+' : '-';
						mode = modeAdjust.get(i).getMode();
						arg = modeAdjust.get(i).getArgument();
								
						cw.addChatLine("Mode:" + adj + mode + " " + arg, Fonts.getFont(Fonts.CHATSTATUS));
						cw.channelMode(mode, plus, arg);
					}
				}
				
				
			} else if (me.getModeType() == ModeEvent.ModeType.USER) {
				System.out.println("Got an usermode MODE-EVENT");
				System.out.println(me.getRawEventData());
			}
			
		//If someone got kicked
		} else if (e.getType() == Type.KICK_EVENT) {
			KickEvent ke = (KickEvent) e;
			ChannelWindow cw = channels.get(ke.getChannel().getName());
			cw.removeNick(ke.getUserName());
			cw.addChatLine(ke.getUserName() + " (" + ke.getHostName() + ") " + 
					messages.getString("ircEventListenerKicked") +  " " + ke.getWho(), 
					Fonts.getFont(Fonts.SERVER));
			if(ke.getUserName().equals(ke.getSession().getNick()))
				Main.win.tree.removeChannel(ke.getSession().getConnectedHostName(), cw);
			
		//A default catch, to make unhandled events visible in the console. Solely used for debugging
		//  purposes. Could probably be removed when we release the software... :)
		//} else {
		//	System.out.println("ServerEventListener - unhandeled event: " + e.getType() + " " + e.getRawEventData());
		}
		//System.out.println("ServerEventListener - event: " + e.getType() + " " + e.getRawEventData());
	}
	
	/**
	 * Spawns a new window for private messages.
	 * 
	 * @param session reference to the corresponding session
	 * @param nick The nick we are going to converse with
	 */
	public void privMessageWindow(Session session, String nick) {
		ChannelWindow channel = channels.get(nick);
		
		if(channel == null) {
			channel = new ChannelWindow(session, 
					session.getConnectedHostName() + " - " + nick, 
					nick, ChannelWindow.PRIVATE_CHAT);
			channels.put(nick, channel);
			Main.win.addMDIWin(channel);
			Main.win.tree.addChannel(session.getConnectedHostName(), channel);
		} else {
			int j = 0;
			JInternalFrame[] frames = Main.win.md.getAllFrames();
			
			for(int i = 0; i < frames.length; i++) {
				if(frames[i] == channel) {
					j = 1;
					break;
				}
			}
			if(j == 0)
				Main.win.md.add(channel);
			
			//Window state handler
			if (channel.isIcon()) {
				try {
					channel.setIcon(false);
				} catch (PropertyVetoException e) {
					e.printStackTrace();
				}
			}
			channel.toFront(); 
		}
	}
	
	/**
	 * Sets the ResourceBundle used for internationalization.
	 * 
	 * @param m - ResourceBundle
	 */
	public static void setMessages(ResourceBundle m){
		messages = m;
	}
}
